using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using FernUniHagen.CompilerExtension.DataStructures;
using FernUniHagen.CompilerExtension.Attributes;
using Roslyn.Compilers;
using Roslyn.Compilers.Common;
using Roslyn.Compilers.CSharp;
using FernUniHagen.CompilerExtension.Functions;

namespace FernUniHagen.CompilerExtension.Converter.Block
{
    public class CastExpressionConverter : BlockNodeFactory
    {
      

        /// <summary>
        /// Applys down or upcast depending on the desired cast
        /// </summary>
        /// <returns>list of statements</returns>
        public override List<StatementSyntax> ConvertBlockNode()
        {
            var statementsToAdd = new List<StatementSyntax>();
            //TODO: if time left switch to fuly qualified name
            
            var typeSymbol = SemanticModel.GetTypeInformation( NodeToReplaceInBlock.ReferencedNode);
            INamedTypeSymbol multiplicityTypeSymbol = SemanticModel.Compilation.GetTypeByMetadataName("Roslyn.Compiler.Extension.Types.IMultiplicityAttribute");

            //We deal with a multiplicity cast - Apply M-Cast rules
            if (typeSymbol.Interfaces.Contains(multiplicityTypeSymbol))
            {

                var typeToCastTo = (NodeToReplaceInBlock.ReferencedNode as CastExpressionSyntax).Type;
                var anyAttributeType = Syntax.ParseName("AnyAttribute");

                if (anyAttributeType.EquivalentTo(typeToCastTo))
                    statementsToAdd = ApplyUpCast();
                else
                    statementsToAdd = ApplyDownCast();
            }
            //We deal with a type cast - Apply R-TCast Rule
            else
            {
                statementsToAdd = ApplyTypeCast();
            }

            return statementsToAdd;

        }

        private List<StatementSyntax> ApplyTypeCast()
        {
            var statementsToAdd = new List<StatementSyntax>();
            var castExpression = (CastExpressionSyntax)NodeToReplaceInBlock.ReferencedNode;

            var variableToCast = (IdentifierNameSyntax)castExpression.Expression.DescendantNodesAndSelf().First(n => n is IdentifierNameSyntax);
            var declaration = SemanticModel.FindDecleration(variableToCast);
            AttributeType multiplicityType = AttributeType.None;
            GenericNameSyntax genericName = null;
            Utilities.GetMultiplicityAttribute(declaration, out multiplicityType, out genericName);
            var castedGenericType = Utilities.CreateGenericType(castExpression.Type, genericName);


            if (multiplicityType == AttributeType.Any)
            {
                string listDummyVar = VariableCreatorService.GetTempVariable();
                string loopVar = VariableCreatorService.GetTempVariable();

                var dummyListStmt = Syntax.ParseStatement(string.Format("{0} {1} = new {0}();" + Environment.NewLine, castedGenericType, listDummyVar));
                var foreachLoop = Syntax.ParseStatement(string.Format("foreach(var {0} in {1}) {2}.Add(({3}){0}) ;" + Environment.NewLine,
                    loopVar, variableToCast, listDummyVar, castExpression.Type));
                var newAssignExpression = Syntax.ParseExpression(listDummyVar + ";");

                StatementReplacements = new StatementReplacement<CommonSyntaxNode>(castExpression, newAssignExpression);
              //  StatementReplacements.Add(new StatementReplacement(castExpression, newAssignExpression));

                statementsToAdd.AddRange(dummyListStmt, foreachLoop);
            }
            else
            {
                StatementReplacements = new StatementReplacement<CommonSyntaxNode>(NodeToReplaceInBlock.Statement, NodeToReplaceInBlock.Statement);
            //    StatementReplacements.Add(new StatementReplacement(NodeToReplaceInBlock.Statement, NodeToReplaceInBlock.Statement));
               // statementsToAdd.Add(NodeToReplaceInBlock.Statement);
            }
            return statementsToAdd;
        }


        /// <summary>
        /// Implements down cast from multiplicity any to another multiplicity less than any
        /// Basically applys rule R-MCast1. But the implementation was based on the thought of Steimann Document Onward2013
        /// </summary>
        /// <param name="blockSyntax"></param>
        /// <param name="nodeToReplaceInBlock"></param>
        /// <returns></returns>
        private List<StatementSyntax> ApplyDownCast()
        {
            var statementsToAdd = new List<StatementSyntax>();

            CastExpressionSyntax multiplicityCastExp = (CastExpressionSyntax)NodeToReplaceInBlock.ReferencedNode;
            var typeCast = (CastExpressionSyntax)multiplicityCastExp.DescendantNodes().First(n => n is CastExpressionSyntax);

            string typeOfExpression = typeCast.Type.ToString();
            string expressionVariable = VariableCreatorService.GetTempVariable();

            var expressionSyntax = multiplicityCastExp.Expression;
            var newResult = VariableCreatorService.GetTempVariable();

            var variableDeclaration = Syntax.ParseStatement(string.Format("{0} {1};" + Environment.NewLine, typeOfExpression, newResult));
            var assignExpr = Syntax.ParseStatement(string.Format("var {0} = {1};" + Environment.NewLine, expressionVariable, typeCast.Expression));
            var conditionStmt = Syntax.ParseStatement(string.Format(
                "if({0}.Count == 0) {{ {1} = null; }}{2} " +
                "else if({0}.Count == 1) {{ {3} = {0}.First();{2} }}" +
                "else throw new MultiplicityCastException(\"Multiplicity Exception\");{2}", expressionVariable, newResult, Environment.NewLine, newResult));
            var newAssignExpression = Syntax.ParseExpression(newResult + ";");
            

            statementsToAdd.AddRange(variableDeclaration, assignExpr, conditionStmt);
            StatementReplacements = new StatementReplacement<CommonSyntaxNode>(NodeToReplaceInBlock.ReferencedNode, newAssignExpression);
          //  StatementReplacements.Add(new StatementReplacement(NodeToReplaceInBlock.ReferencedNode, newAssignExpression));
            return statementsToAdd;
        }

        /// <summary>
        /// Applies the Upcast from bare, option etc. to multiplicity any 
        /// Implements the rules for R-MCast2 and R-MCast3
        /// </summary>
        /// <param name="blocksyntax">the block where the cast shall take place</param>
        /// <param name="nodeToReplaceInBlock">the node to replace in the block</param>
        /// <returns>upcasted node</returns>
        private List<StatementSyntax> ApplyUpCast()
        {
            var statementsToAdd = new List<StatementSyntax>();
            var multiplicityCast = ((CastExpressionSyntax)NodeToReplaceInBlock.ReferencedNode);
            var collectionCast = ((CastExpressionSyntax)multiplicityCast.Expression);
            var typeInfo = SemanticModel.GetTypeInfo(collectionCast.Expression);


            var variableToCast = (IdentifierNameSyntax)collectionCast.Expression.DescendantNodesAndSelf().First(n => n is IdentifierNameSyntax);
            var declaration = SemanticModel.FindDecleration(variableToCast);
            var attributeType = Utilities.GetMultiplicityAttribute(declaration);


            //Apply Rule R-MCast2
            if (attributeType == AttributeType.Any)
            {
                var newExpression = Syntax.ParseExpression(string.Format("new {0}<{1}>() {{{2}}};", collectionCast.Type.ToString(),
                    typeInfo.Type, collectionCast.Expression.ToString()));
                //  statementsToAdd.Add(NodeToReplaceInBlock.Statement.ReplaceNode(NodeToReplaceInBlock.ReferencedNode, newExpression));
                StatementReplacements = new StatementReplacement<CommonSyntaxNode>(NodeToReplaceInBlock.ReferencedNode, newExpression);
             //   StatementReplacements.Add(new StatementReplacement(NodeToReplaceInBlock.ReferencedNode, newExpression));
            }
            //Apply Rule R-MCast3
            else
            {
                string listVariable = VariableCreatorService.GetTempVariable();
                string tempVariable = VariableCreatorService.GetTempVariable();
                var initList = Syntax.ParseStatement(string.Format("{0}<{1}> {2} = new {0}<{1}>();" + Environment.NewLine,
                    collectionCast.Type.ToString(), typeInfo.Type, listVariable));
                var storeVariable = Syntax.ParseStatement(string.Format("var {0} = {1};" + Environment.NewLine, tempVariable, collectionCast.Expression));
                var condition = Syntax.ParseStatement(string.Format("if({0} != null) {1}.Add({0});" + Environment.NewLine, tempVariable, listVariable));
                var newAssignExpression = Syntax.ParseExpression(listVariable + ";");
                //var newCastStatement = NodeToReplaceInBlock.Statement.ReplaceNode(NodeToReplaceInBlock.ReferencedNode, newAssignExpression);
                //newCastStatement = newCastStatement.WithLeadingTrivia(Syntax.Space);
                //newCastStatement = newCastStatement.WithTrailingTrivia(Syntax.Space);
                statementsToAdd.AddRange(initList, storeVariable, condition);
                StatementReplacements = new StatementReplacement<CommonSyntaxNode>(NodeToReplaceInBlock.ReferencedNode, newAssignExpression);
               // StatementReplacements.Add(new StatementReplacement(NodeToReplaceInBlock.ReferencedNode, newAssignExpression));
            }


            return statementsToAdd;
        }
    }
}
